package pl.edu.pw.ee.buildcity.scm.subversion;

import org.apache.log4j.Logger;
import org.tmatesoft.svn.core.*;
import org.tmatesoft.svn.core.wc.*;
import pl.edu.pw.ee.buildcity.entities.SCMCommit;
import pl.edu.pw.ee.buildcity.entities.SVNCommit;
import pl.edu.pw.ee.buildcity.scm.SCMConnector;
import pl.edu.pw.ee.buildcity.scm.SCMException;

import javax.annotation.PostConstruct;
import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;

/**
 * @author mgrzechocinski
 * @since 2009-09-19
 */
public class SubversionServiceImpl implements SubversionService {

	private static final Logger log = Logger.getLogger(SubversionServiceImpl.class);

	private final SVNClientManager clientManager;

	public SubversionServiceImpl(SVNClientManager clientManager) {
		this.clientManager = clientManager;
	}

	@SuppressWarnings("unused")
	@PostConstruct
	private void registerConnectors() {
		for (SCMConnector scmConnector : SCMConnector.values()) {
			scmConnector.setup();
		}
	}

	@Override
	public File checkout(SVNCheckoutParameters p, String workingCopyLocation) throws SCMException {
		try {
			log.debug("Trying to checkout project to " + workingCopyLocation + " by given parameters: " + p);
			File workingCopy = new File(workingCopyLocation);

			SVNURL svnurl = SVNURL.create(p.connector.getProtocol(), null, p.host, p.port, p.pathToCheckout, false);
			clientManager.getUpdateClient().doCheckout(svnurl, workingCopy, null, p.revisionToCheckout, true);

			log.info("Project " + p.pathToCheckout + " was successfully checkouted to " + workingCopy);
			return workingCopy;

		} catch (Exception e) {
			log.fatal(e.getMessage(), e);
			throw new SCMException(e);
		}
	}

	@Override
	public boolean changeDetected(File workingCopy) throws SCMException {
		try {
			log.debug("Checking if WC '" + workingCopy + "' needs to be updated from repository...");
			long currentWcRevision = getCurrentWorkingCopyRevision(workingCopy);
			long headRevision = getWorkingCopyRevision(workingCopy, SVNRevision.HEAD);

			log.debug("WC: " + workingCopy + ", current revision: " + currentWcRevision + ", HEAD: " + headRevision);
			return headRevision != currentWcRevision;
		} catch (Exception e) {
			log.fatal(e.getMessage(), e);
			throw new SCMException(e);
		}
	}

	@Override
	public void updateToHEAD(File workingCopy) throws SCMException {
		try {
			clientManager.getUpdateClient().doUpdate(workingCopy, SVNRevision.HEAD, true);
			log.debug("WC: " + workingCopy + " successfully updated to HEAD revision");
		} catch (Exception e) {
			log.fatal(e.getMessage(), e);
			throw new SCMException(e);
		}
	}

	@Override
	public List<SCMCommit> getLogBetweenHEADAndWorkingCopyRevision(File workingCopy) throws SCMException {
		try {
			SVNLogClient logClient = clientManager.getLogClient();
			File[] files = new File[]{workingCopy};
			WorkingCopiesLogEntryHandler wcLogger = new WorkingCopiesLogEntryHandler();

			logClient.doLog(files, SVNRevision.WORKING, SVNRevision.HEAD, false, true, Integer.MAX_VALUE, wcLogger);
			log.debug("Found following log entries for " + workingCopy + ": " + Arrays.deepToString(wcLogger.getLogEntries().toArray()));

			return wcLogger.getLogEntries();

		} catch (Exception e) {
			log.fatal(e.getMessage(), e);
			throw new SCMException(e);
		}
	}

	private SCMCommit rewrite(SVNLogEntry logEntry) {
		SVNCommit svnChange = new SVNCommit();
		svnChange.byAuthor(logEntry.getAuthor());
		svnChange.onDate(logEntry.getDate());
		svnChange.withComment(logEntry.getMessage());
		svnChange.withRevision(logEntry.getRevision());

		StringBuilder sb = new StringBuilder();
		Map<String, SVNLogEntryPath> changedPaths = logEntry.getChangedPaths();

		if (changedPaths != null) {
			for (Object o : logEntry.getChangedPaths().values()) {
				SVNLogEntryPath value = (SVNLogEntryPath) o;

				sb.append(value.getType()).append(" ");
				sb.append(value.getPath()).append(" ");
				if (value.getCopyPath() != null) {
					sb.append("(from " + value.getCopyPath() + ", revision: " + value.getCopyRevision() + ")").append(" ");
				}
			}
		}
		svnChange.withChanges(sb.toString());
		return svnChange;
	}

	private long getCurrentWorkingCopyRevision(File workingCopy) throws SVNException {
		return getWorkingCopyRevision(workingCopy, SVNRevision.WORKING);
	}

	private long getWorkingCopyRevision(File workingCopy, SVNRevision revision) throws SVNException {
		WorkingCopyInfoHandler wcInfoHandler = new WorkingCopyInfoHandler();
		clientManager.getWCClient().doInfo(workingCopy, revision, true, wcInfoHandler);
		return wcInfoHandler.getWCRevision().getNumber();
	}

	private class WorkingCopiesLogEntryHandler implements ISVNLogEntryHandler {

		private final List<SCMCommit> logEntries = new ArrayList<SCMCommit>();

		@Override
		public void handleLogEntry(SVNLogEntry logEntry) throws SVNException {
			logEntries.add(rewrite(logEntry));
		}

		public List<SCMCommit> getLogEntries() {
			return logEntries;
		}
	}

	;

	private class WorkingCopyInfoHandler implements ISVNInfoHandler {

		private SVNRevision wcRevision;

		@Override
		public void handleInfo(SVNInfo info) throws SVNException {
			wcRevision = info.getRevision();
		}

		public SVNRevision getWCRevision() {
			return wcRevision;
		}
	}
}
